<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=783129
-->
<head>
  <title>Test for document.registerElement lifecycle callback</title>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
<div id="container">
</div>
<script>

var container = document.getElementById("container");

// Test changing attributes in the created callback on an element
// created after registration.
function testChangeAttributeInCreatedCallback() {
  var createdCallbackCalled = false;
  var attributeChangedCallbackCalled = false;

  var p = Object.create(HTMLElement.prototype);
  p.createdCallback = function() {
    is(createdCallbackCalled, false, "Created callback should be called before attached callback.");
    createdCallbackCalled = true;
    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not have been called prior to setting the attribute.");
    this.setAttribute("foo", "bar");
    is(attributeChangedCallbackCalled, true, "While element is being created, element should be added to the current element callback queue.");
    runNextTest();
  };

  p.attributeChangedCallback = function(name, oldValue, newValue) {
    is(createdCallbackCalled, true, "attributeChanged callback should be called after the created callback because it was enqueued during created callback.");
    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
    is(newValue, "bar", "The new value should be 'bar'");
    attributeChangedCallbackCalled = true;
  };

  document.registerElement("x-one", { prototype: p });
  document.createElement("x-one");
}

function testChangeAttributeInEnteredViewCallback() {
  var p = Object.create(HTMLElement.prototype);
  var attributeChangedCallbackCalled = false;
  var attachedCallbackCalled = false;

  p.attachedCallback = function() {
    is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
    attachedCallbackCalled = true;
    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not be called before changing attribute.");
    this.setAttribute("foo", "bar");
    is(attributeChangedCallbackCalled, true, "Transition from user-agent implementation to script should result in attribute changed callback being called.");
    runNextTest();
  };

  p.attributeChangedCallback = function() {
    is(attachedCallbackCalled, true, "attached callback should have been called prior to attribute changed callback.");
    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
    attributeChangedCallbackCalled = true;
  };

  document.registerElement("x-two", { prototype: p });
  var elem = document.createElement("x-two");

  var container = document.getElementById("container");
  container.appendChild(elem);
}

function testLeaveViewInEnteredViewCallback() {
  var p = Object.create(HTMLElement.prototype);
  var attachedCallbackCalled = false;
  var detachedCallbackCalled = false;
  var container = document.getElementById("container");

  p.attachedCallback = function() {
    is(this.parentNode, container, "Parent node should the container in which the node was appended.");
    is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
    attachedCallbackCalled = true;
    is(detachedCallbackCalled, false, "detached callback should not be called prior to removing element from document.");
    container.removeChild(this);
    is(detachedCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback.");
    runNextTest();
  };

  p.detachedCallback = function() {
    is(detachedCallbackCalled, false, "The detached callback should only be called once in this test.");
    is(attachedCallbackCalled, true, "The attached callback should be called prior to detached callback.");
    detachedCallbackCalled = true;
  };

  document.registerElement("x-three", { prototype: p });
  var elem = document.createElement("x-three");

  container.appendChild(elem);
}

function testStackedAttributeChangedCallback() {
  var p = Object.create(HTMLElement.prototype);
  var attributeChangedCallbackCount = 0;

  var attributeSequence = ["foo", "bar", "baz"];

  p.attributeChangedCallback = function(attrName, oldValue, newValue) {
    if (newValue == "baz") {
      return;
    }

    var nextAttribute = attributeSequence.shift();
    ok(true, nextAttribute);
    // Setting this attribute will call this function again, when
    // control returns to the script, the last attribute in the sequence should
    // be set on the element.
    this.setAttribute("foo", nextAttribute);
    is(this.getAttribute("foo"), "baz", "The last value in the sequence should be the value of the attribute.");

    attributeChangedCallbackCount++;
    if (attributeChangedCallbackCount == 3) {
      runNextTest();
    }
  };

  document.registerElement("x-four", { prototype: p });
  var elem = document.createElement("x-four");
  elem.setAttribute("foo", "changeme");
}

var testFunctions = [
  testChangeAttributeInCreatedCallback,
  testChangeAttributeInEnteredViewCallback,
  testLeaveViewInEnteredViewCallback,
  testStackedAttributeChangedCallback,
  SimpleTest.finish
];

function runNextTest() {
  if (testFunctions.length > 0) {
    var nextTestFunction = testFunctions.shift();
    nextTestFunction();
  }
}

SimpleTest.waitForExplicitFinish();

runNextTest();

</script>
</body>
</html>
